﻿using System;

namespace FixedWidth.Configuration.Rules
{
	public abstract class ColumnRule : IColumnRule
	{
		/// <summary>
		/// The position in the row to start parsing with.
		/// </summary>
		public virtual int Position
		{
			get
			{ return _Position; }
			set
			{
				if (value < 0) throw new ArgumentOutOfRangeException("The position argument cannot be less than zero.");
				_Position = value;
			}
		}
		private int _Position = 0;

		/// <summary>
		/// The length of the string to parse.
		/// </summary>
		public virtual int Length
		{
			get { return _Length; }
			set
			{
				if (value < 1) throw new ArgumentOutOfRangeException("The length property cannot be less than 1.");
				_Length = value;
			}
		}
		private int _Length = 1;

		/// <summary>
		/// The name of the column.
		/// </summary>
		public virtual string Name
		{
			get { return _Name; }
			set
			{
				if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("The column rule name cannot be null.");
				_Name = value;
			}
		}
		private string _Name = string.Empty;

		/// <summary>
		/// The type of the column.
		/// </summary>
		public virtual Type Type { get; protected set; }

		public abstract object Parse(string toParse);
	}

	/// <summary>
	/// A strongly-typed column rule.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class ColumnRule<T> : ColumnRule, IColumnRule<T>
	{
		private static readonly Common.Logging.ILog Logger = Common.Logging.LogManager.GetCurrentClassLogger();

		public ColumnRule()
		{
			Configure = new RuleFluentInterface<T>(this);
			Length = 1;
			this.Type = typeof(T);
		}

		/// <summary>
		/// Fluently configure the column rule.
		/// </summary>
		public virtual RuleFluentInterface<T> Configure { get; protected set; }

		/// <summary>
		/// Provide the lambda expression on how to parse the string.
		/// </summary>
		public virtual Func<string, T> ParsingFunction
		{
			get
			{
				return _ParsingFunction;
			}
			set
			{
				if (value == null) throw new ArgumentNullException("The parsing function cannot be null.");
				_ParsingFunction = value;
			}
		}
		private Func<string, T> _ParsingFunction = null;

		/// <summary>
		/// Parses the string and extracts the value.
		/// </summary>
		/// <param name="rowdata">The row data.</param>
		/// <returns></returns>
		public T ParseTyped(string rowdata)
		{
			//make sure we have a parsing function to play with
			if (ParsingFunction == null) throw new InvalidOperationException("The parsing function has not been defined; cannot continue.");
			if (Position >= rowdata.Length) throw new EndOfRowException("The position to read is beyond the end of the data.");
			if (string.IsNullOrWhiteSpace(Name)) throw new InvalidOperationException("The name for the column rule has not been defined.");

			Logger.Trace(t => t("Parsing column from position {0} to position {1} of: '{2}'", 
				this.Position, this.Position + this.Length, rowdata));
			try
			{
				string toParse;
				if ((Position + Length) > rowdata.Length)
				{
					toParse = rowdata.Substring(this.Position);
				}
				else
				{
					toParse = rowdata.Substring(this.Position, this.Length);
				}
				Logger.Trace(t => t("Column string to parse: {0}", toParse));
				var value = ParsingFunction.Invoke(toParse);
				Logger.Trace(t => t("Value parsed."));
				return value;
			}
			catch (Exception ex)
			{
				Logger.Trace(t => t("Parse failed with message: {0}", ex.Message));
				throw new ParseException(string.Format("Could not parse data for column '{0}'", this.Name), ex);
			}
		}

		/// <summary>
		/// Overrides the behavior of the base class.
		/// </summary>
		/// <param name="toParse"></param>
		/// <returns></returns>
		public override object Parse(string toParse)
		{
			return this.ParseTyped(toParse);
		}

		/// <summary>
		/// Fluent configuration interface.
		/// </summary>
		/// <typeparam name="T2"></typeparam>
		public class RuleFluentInterface<T2>
		{
			private readonly ColumnRule<T2> _rule;

			public RuleFluentInterface(ColumnRule<T2> rule)
			{
				_rule = rule;
			}

			/// <summary>
			/// Set the zero-based text position to start reading from.
			/// </summary>
			/// <param name="position"></param>
			/// <returns></returns>
			public RuleFluentInterface<T2> Position(int position)
			{
				_rule.Position = position;
				return this;
			}

			/// <summary>
			/// Set the length of the string to read.
			/// </summary>
			/// <param name="length"></param>
			/// <returns></returns>
			public RuleFluentInterface<T2> Length(int length)
			{
				_rule.Length = length;
				return this;
			}

			/// <summary>
			/// Set the name of the column for the data.
			/// </summary>
			/// <param name="name"></param>
			/// <returns></returns>
			public RuleFluentInterface<T2> Name(string name)
			{
				_rule.Name = name;
				return this;
			}

			/// <summary>
			/// Set the lambda expression of how to parse the data.
			/// </summary>
			/// <param name="parsingFunction"></param>
			/// <returns></returns>
			public RuleFluentInterface<T2> ParsingFunction(Func<string, T2> parsingFunction)
			{
				_rule.ParsingFunction = parsingFunction;
				return this;
			}

			/// <summary>
			/// Explicitly return the ColumnRule object.
			/// </summary>
			/// <returns></returns>
			public ColumnRule<T2> Build()
			{
				return _rule;
			}

			public static implicit operator ColumnRule<T2>(RuleFluentInterface<T2> config)
			{
				return config._rule;
			}
		}
	}
}
